extern crate reqwest;
extern crate serde_json;

use super::encrypt::{Cryptor, Encryptor};
use std::collections::HashMap;
use std::fmt;

use colored::Colorize;
use log::error;
use serde::Deserialize;
use net_brd::ChainNet;
use net_brd::Chain;
use net_brd::ChainData;

// use crate::encrypt::En
// use std::time::SystemTime;

macro_rules! dict{
    ( $($k:ident :$v:expr),* ) => {
        [
            $((stringify!($k), $v.to_string())),*
        ].iter().cloned().collect();
    };
}

#[derive(Deserialize)]
pub struct Reply {
    pub status: u32,
    pub msg: String,
}

impl Reply {
    fn try_decrypt(&mut self, cipher: &Encryptor) {
        self.msg = String::from_utf8(cipher.de_b64(&self.msg)).expect("decrypt resply error !");
    }
}
// fn mark_color

impl fmt::Display for Reply {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let msg = self
            .msg
            .replace(
                "injectable",
                "injectable".green().bold().to_string().as_str(),
            )
            .replace("not", "not".red().bold().to_string().as_str());
        if self.status == 0 {
            write!(f, "{}", msg)
        } else {
            write!(f, "{}", msg)
        }
    }
}

pub fn json_to(
    op: &str,
    data: &str,
    tp: &str,
    sub: &str,
    host: Option<&str>,
    cipher: Option<&Encryptor>,
    brd: Option<bool>,
) -> Reply {
    let mut url: String;
    let data_to: String;
    let client = reqwest::Client::new();
    let use_enc: String;

    let mut json_reply: Reply;
    let mut data_j: HashMap<&str, String>;

    if let Some(host) = host {
        if host.starts_with("http") {
            url = format!("{}", host)
        } else {
            url = format!("http://{}", host);
        }
    } else {
        url = String::from("http://localhost:8000")
    }
    url.push_str(sub);
    data_j = dict! {
        op : op,
        data : data,
        data_tp : tp
    };

    if let Some(cry) = cipher {
        use_enc = String::from("t");
        data_to = cry.en_b64(data.as_bytes());
        data_j.insert("data", data_to);
        data_j.insert("encrypt", use_enc);
    }
    if let Some(brd) = brd {
        if brd {
            data_j.insert("brd", sub.to_string());
        }
    }

    let mut res = match client.post(&url).json(&data_j).send() {
        Ok(res) => res,
        Err(e) => {
            json_reply = Reply {
                status: 1, // request error net error
                msg: format!("[req][x] {} \nreson: {}", url, e),
            };
            return json_reply;
        }
    };
    // content error
    if res.status() != 200 {
        json_reply = Reply {
            status: 2,
            msg: res.text().expect("text extract error"),
        };
        return json_reply;
    }

    // println!("{}", res.text().expect("text errr"));
    json_reply = match res.json() {
        Ok(e) => e,
        _ => {
            let mut r = match client.post(&url).json(&data_j).send() {
                Ok(a) => a,
                Err(a) => {
                    error!("err : {}", a);
                    return Reply {
                        status: 1,
                        msg: "send network error".to_string(),
                    };
                }
            };
            json_reply = Reply {
                status: 2,
                msg: r.text().expect("text extract error"),
            };
            if let Some(cry) = cipher {
                json_reply.try_decrypt(cry);
            }
            return json_reply;
        }
    };
    if let Some(cry) = cipher {
        json_reply.try_decrypt(cry);
    }
    // println!("{}",json_reply);
    json_reply
}


pub struct Client{
    encryptor: Encryptor
}

impl Default for Client{
    fn default()   -> Self{
        Self{
            encryptor: Encryptor::new("fuck you world!")
        }
    }
}

impl ChainNet for Client{
    fn action(&self, chain:&Chain) -> Option<Chain>{
        let content = chain.to_json();
        let mut if_brd = false;
        if chain.task.output_tp & net_brd::OUTPUT_TO_MANY_BRD != 0{
            if_brd = true
        }
        let repl = json_to(
            "chain",
            &content,
            &chain.action,
            "/chain",
            Some(&chain.loc.host),
            Some(&self.encryptor),
            Some(if_brd)
        );

        repl.msg.chain_load_json()
    }
}
